using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Reflection;

namespace WebServiceClientGenerator
{
    /// <summary>
    /// Utility methods for generating source code using CodeDom.
    /// </summary>
    /// <author>Kenneth Xu</author>
    public static class CodeUtils
    {
        /// <summary>
        /// Single dimension array access expression.
        /// </summary>
        /// <param name="array">The array.</param>
        /// <param name="index">The index.</param>
        /// <returns>
        /// An instance of <see cref="CodeArrayIndexerExpression"/>
        /// </returns>
        public static CodeArrayIndexerExpression ArrayIndex(
            CodeExpression array, int index)
        {
            return new CodeArrayIndexerExpression(
                array, new CodePrimitiveExpression(index));
        }

        /// <summary>
        /// Determine the if a parameter is <see langword="ref"/> or 
        /// <see langword="out"/>.
        /// </summary>
        /// <param name="parameter">The parameter from reflection.</param>
        /// <returns>Parameter direction</returns>
        public static FieldDirection DetermineParameterDirection(
            ParameterInfo parameter)
        {
            FieldDirection direction;
            if (parameter.IsOut)
                direction = FieldDirection.Out;
            else if (parameter.ParameterType.IsByRef)
                direction = FieldDirection.Ref;
            else
                direction = FieldDirection.In;
            return direction;
        }

        /// <summary>
        /// Define a new method.
        /// </summary>
        /// <param name="attributes">Custom attributes.</param>
        /// <param name="modifier">Access modifier.</param>
        /// <param name="returnType">Data type to return</param>
        /// <param name="name">Name of the method.</param>
        /// <param name="parameters">
        /// Parameter definition of this method.
        /// </param>
        /// <returns>A <see cref="CodeMemberMethod"/>.</returns>
        public static CodeMemberMethod DefineMethod(
            CodeAttributeDeclaration[] attributes,
            MemberAttributes modifier,
            CodeTypeReference returnType,
            string name,
            params CodeParameterDeclarationExpression[] parameters
            )
        {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = name;
            method.Attributes = modifier;
            method.ReturnType = returnType;
            method.CustomAttributes.AddRange(attributes);
            method.Parameters.AddRange(parameters);
            return method;
        }

        /// <summary>
        /// Define a local variable in a <paramref name="method"/>.
        /// </summary>
        /// <param name="method">
        /// The method that new varaible will be defined in.
        /// </param>
        /// <param name="type">The type of the variable</param>
        /// <param name="name">The name of the variable</param>
        /// <param name="initializer">The variable initializer.</param>
        /// <returns>
        /// A <see cref="CodeVariableReferenceExpression"/> that can be used
        /// to refer to the defined variable.
        /// </returns>
        public static CodeVariableReferenceExpression DefineVariable(
            CodeMemberMethod method,
            CodeTypeReference type,
            string name,
            CodeExpression initializer)
        {
            CodeVariableDeclarationStatement statement =
                new CodeVariableDeclarationStatement(type, name, initializer);
            method.Statements.Add(statement);
            return new CodeVariableReferenceExpression(name);
        }

        /// <summary>
        /// Define a parameter for the given <paramref name="method"/>.
        /// </summary>
        /// <param name="method">The method to define the paramter.</param>
        /// <param name="type">The type of the parameter.</param>
        /// <param name="name">The name of the parameter.</param>
        /// <returns>
        /// A <see cref="CodeVariableReferenceExpression"/> that can be used
        /// to refer to defined parameter.
        /// </returns>
        public static CodeVariableReferenceExpression DefineParameter(
            CodeMemberMethod method,
            CodeTypeReference type,
            string name)
        {
            return DefineParameter(method, type, FieldDirection.In, name);
        }

        /// <summary>
        /// Define a parameter for the given <paramref name="method"/>.
        /// Optionally the parameter can be <see langword="out"/> or
        /// <see langword="ref"/>.
        /// </summary>
        /// <param name="method">The method to define the paramter.</param>
        /// <param name="type">The type of the parameter.</param>
        /// <param name="direction">To specify ref or out parameter.</param>
        /// <param name="name">The name of the parameter.</param>
        /// <returns>
        /// A <see cref="CodeVariableReferenceExpression"/> that can be used
        /// to refer to defined parameter.
        /// </returns>
        public static CodeVariableReferenceExpression DefineParameter(
            CodeMemberMethod method,
            CodeTypeReference type,
            FieldDirection direction,
            string name)
        {
            CodeParameterDeclarationExpression parameter =
                new CodeParameterDeclarationExpression(type, name);
            parameter.Direction = direction;
            method.Parameters.Add(parameter);
            return new CodeVariableReferenceExpression(name);
        }

        /// <summary>
        /// Define a new class without custom attribute. It has no base 
        /// class and doesn't implement any interface.
        /// </summary>
        /// <param name="modifier">Access modifier</param>
        /// <param name="name">Class name</param>
        /// <param name="typeParameters">
        /// Optional type parameter if this is a generic class.
        /// </param>
        /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
        public static CodeTypeDeclaration DefineClass(
            TypeAttributes modifier,
            string name,
            params CodeTypeParameter[] typeParameters)
        {
            return DefineClass(
                null, modifier, name, typeParameters, null, null);
        }

        /// <summary>
        /// Define a new non-generic class with no custom attributes.
        /// </summary>
        /// <remarks>
        /// Generic type parameters and custom attributes can be added later.
        /// </remarks>
        /// <param name="modifier">Access modifier</param>
        /// <param name="name">Class name</param>
        /// <param name="baseType">The base class.</param>
        /// <param name="interfaces">Optional interfaces to implement.</param>
        /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
        public static CodeTypeDeclaration DefineClass(
            TypeAttributes modifier,
            string name,
            CodeTypeReference baseType,
            params CodeTypeReference[] interfaces)
        {
            return DefineClass(
                null, modifier, name, null, baseType, interfaces);
        }

        /// <summary>
        /// Define a new non-generic class.
        /// </summary>
        /// <remarks>
        /// Generic type parameters can be added later.
        /// </remarks>
        /// <param name="attributes">Custome attributes for the class.</param>
        /// <param name="modifier">Access modifier</param>
        /// <param name="name">Class name</param>
        /// <param name="baseType">The base class, or null.</param>
        /// <param name="interfaces">Optional interfaces to implement.</param>
        /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
        public static CodeTypeDeclaration DefineClass(
            CodeAttributeDeclaration[] attributes,
            TypeAttributes modifier,
            string name,
            CodeTypeReference baseType,
            params CodeTypeReference[] interfaces)
        {
            return DefineClass(
                attributes, modifier, name, null, baseType, interfaces);
        }

        /// <summary>
        /// Define a new class.
        /// </summary>
        /// <param name="attributes">Custome attributes for the class.</param>
        /// <param name="modifier">Access modifier</param>
        /// <param name="name">Class name</param>
        /// <param name="typeParameters">
        /// Type parameter if this is a generic class, null otherwise.
        /// </param>
        /// <param name="baseType">The base class.</param>
        /// <param name="interfaces">The interfaces to implement.</param>
        /// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
        public static CodeTypeDeclaration DefineClass(
            CodeAttributeDeclaration[] attributes,
            TypeAttributes modifier,
            string name,
            CodeTypeParameter[] typeParameters,
            CodeTypeReference baseType,
            params CodeTypeReference[] interfaces)
        {
            return DefineType(attributes, modifier, TypeType.Class,
                              name, typeParameters, baseType, interfaces);
        }

        private static CodeTypeDeclaration DefineType(
            CodeAttributeDeclaration[] attributes,
            TypeAttributes modifier,
            TypeType typeType,
            string name,
            CodeTypeParameter[] typeParameters,
            CodeTypeReference baseType,
            CodeTypeReference[] interfaces)
        {
            CodeTypeDeclaration ctd = new CodeTypeDeclaration();
            ctd.Name = name;
            ctd.TypeAttributes = modifier;
            switch (typeType)
            {
                case TypeType.Class:
                    ctd.IsClass = true;
                    break;
                case TypeType.Interface:
                    ctd.IsInterface = true;
                    break;
                case TypeType.Struct:
                    ctd.IsStruct = true;
                    break;
                case TypeType.Enum:
                    ctd.IsEnum = true;
                    break;
            }
            if (attributes != null)
            {
                ctd.CustomAttributes.AddRange(attributes);
            }
            if (typeParameters != null)
            {
                ctd.TypeParameters.AddRange(typeParameters);
            }
            if (baseType != null)
            {
                ctd.BaseTypes.Add(baseType);
            }
            if (interfaces != null)
            {
                ctd.BaseTypes.AddRange(interfaces);
            }
            return ctd;
        }

        internal static CodeTypeReference NewGlobalTypeReference(Type type)
        {
            return new CodeTypeReference(type, CodeTypeReferenceOptions.GlobalReference);
        }

        internal static void CheckArgumentNull(object value, string name)
        {
            if (value == null)
            {
                throw new ArgumentNullException(name);
            }
        }

        internal static void CheckStringArgumentEmptyOrNull(string value, string name)
        {
            CheckArgumentNull(value, name);
            if (value.Length == 0)
            {
                throw new ArgumentException(
                    string.Format("Arguement {0} must not be an empty string.", name));
            }
        }

        internal static void CheckCollectionArgumentEmptyOrNull<T>(ICollection<T> c, string name)
        {
            CheckArgumentNull(c, name);
            if (c.Count == 0)
            {
                throw new ArgumentException(
                    string.Format("Argument {0} must not be an empty collection.", name));
            }
        }

        internal static void CheckNullSetter(object value)
        {
            CheckArgumentNull(value, "value");
        }

        internal static void CheckEmptyOrNullStringSetting(string value)
        {
            CheckStringArgumentEmptyOrNull(value, "value");
        }

        internal static void CheckEmptyOrNullCollectionSetter<T>(ICollection<T> value)
        {
            CheckCollectionArgumentEmptyOrNull(value, "value");
        }

        private enum TypeType
        {
            Class,
            Interface,
            Struct,
            Enum
        }
    }
}